<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>运营监控面板</title>
  <style>
    body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; margin: 16px; }
    .card { border:1px solid #e5e7eb; border-radius:10px; padding:12px 14px; margin-bottom:14px; }
    h2 { font-size:16px; margin:0 0 8px; }
    .grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap:12px; }
    .kv { display:flex; justify-content:space-between; font-size:14px; padding:6px 0; border-bottom:1px dashed #eee; }
    .kv:last-child { border-bottom:none; }
    .warn { color:#b45309; }
    .ok { color:#065f46; }
    .small { color:#6b7280; font-size:12px; }
    button { padding:6px 10px; border:1px solid #1a73e8; background:#1a73e8; color:#fff; border-radius:6px; cursor:pointer; }
    /* Feedback table */
    #feedbackWrap { max-height:400px; overflow:auto; font-size:12px; line-height:1.4; }
    table.feedback { border-collapse:collapse; width:100%; }
    table.feedback th, table.feedback td { border:1px solid #e5e7eb; padding:4px 6px; text-align:left; vertical-align:top; }
    table.feedback th { background:#f9fafb; position:sticky; top:0; z-index:1; }
    .monospace { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; }
    .truncate { max-width:360px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  </style>
</head>
<body>
  <div class="card">
    <h2>管理员登录</h2>
    <div style="display:flex;gap:8px;align-items:center;margin-top:8px;flex-wrap:wrap">
  <input id="uidInput" placeholder="管理员账号" style="padding:6px 8px;border:1px solid #ddd;border-radius:6px" />
  <input id="pwdInput" placeholder="密码" type="password" style="padding:6px 8px;border:1px solid #ddd;border-radius:6px" />
      <button id="btnSetUid">设置 userId</button>
      <button id="btnRefresh">刷新</button>
      <span id="status" class="small"></span>
      <button id="btnRefreshFeedback" style="background:#6366f1;border-color:#6366f1">刷新反馈</button>
    </div>
  </div>

  <div id="root"></div>
  <div id="feedbackSection" class="card" style="display:none">
    <h2 style="display:flex;align-items:center;justify-content:space-between;gap:8px">用户反馈 <span id="feedbackCount" class="small"></span></h2>
    <div id="feedbackWrap"></div>
    <div class="small" style="margin-top:6px;">最近最多显示 200 条，最新在前；包含 userId / sid / UA 及 meta</div>
  </div>
  <div id="usersSection" class="card" style="display:none">
    <h2 style="display:flex;align-items:center;justify-content:space-between;gap:8px">注册用户 <span id="usersCount" class="small"></span></h2>
    <div id="usersWrap" class="small"></div>
    <div class="small" style="margin-top:6px;">显示最近 100 条（按创建顺序假设为数组顺序）</div>
  </div>
  <div id="aiGenSection" class="card" style="display:none">
    <h2 style="display:flex;align-items:center;justify-content:space-between;gap:8px">AI 行程生成日志 <span id="aiGenCount" class="small"></span></h2>
    <div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px">
      <button id="btnRefreshAIGen" style="background:#0d9488;border-color:#0d9488">刷新生成日志</button>
      <select id="aiGenLimit" style="padding:4px 6px;border:1px solid #ddd;border-radius:6px">
        <option value="50">最近50</option>
        <option value="100" selected>最近100</option>
        <option value="200">最近200</option>
        <option value="300">最近300</option>
      </select>
    </div>
    <div id="aiGenWrap" class="small" style="max-height:400px;overflow:auto;font-family:ui-monospace,monospace;line-height:1.4"></div>
    <div class="small" style="margin-top:6px;">仅展示精简摘要（首日前两条事件预览）</div>
  </div>

  <script>
    (function(){
      const host = location.hostname;
      const isLocal = host === '127.0.0.1' || host === 'localhost';
      window.APP_CONFIG = window.APP_CONFIG || { apiBase: isLocal ? 'http://127.0.0.1:9000' : '/api' };
    })();
    const API_BASE = (window.APP_CONFIG && window.APP_CONFIG.apiBase) ? String(window.APP_CONFIG.apiBase).replace(/\/$/, '') : '';
    // Lightweight traffic beacon
    (function(){
      try{
        const payload = { path: location.pathname, ref: document.referrer || '', title: document.title || '' };
        fetch(API_BASE + '/rum/collect', {
          method:'POST', keepalive:true, credentials:'include',
          headers: {'Content-Type':'application/json'},
          body: JSON.stringify(payload)
        }).catch(()=>{});
      }catch(_){ }
    })();

  let ADMIN_PWD = '';
  let HAS_UID = false; // 标记是否已成功设置 userId
    async function fetchJSON(path){
      const url = API_BASE + '/' + String(path||'').replace(/^\//,'');
      const headers = {};
      if(ADMIN_PWD) headers['X-Admin-Password'] = ADMIN_PWD;
      const r = await fetch(url, { credentials: 'include', headers }); if(!r.ok) throw new Error(await r.text()); return r.json();
    }
    function el(tag, attrs={}, children=[]) { const e=document.createElement(tag); Object.entries(attrs).forEach(([k,v])=>{ if(k==='class') e.className=v; else e.setAttribute(k,v); }); (Array.isArray(children)?children:[children]).forEach(c=>{ if(typeof c==='string') e.appendChild(document.createTextNode(c)); else if(c) e.appendChild(c); }); return e; }
    function kvRow(k,v){ return el('div', { class:'kv' }, [ el('div',{},k), el('div',{}, String(v)) ]); }
    function fmtBytes(n){ if(!Number.isFinite(n)) return String(n); const u=['B','KB','MB','GB','TB']; let i=0; let x=n; while(x>=1024 && i<u.length-1){ x/=1024; i++; } return x.toFixed(2)+' '+u[i]; }
    function fmtTime(ts){ try{ if(!ts) return ''; const d=new Date(ts*1000); return d.toISOString().replace('T',' ').slice(0,19); }catch{return '';} }

    async function render(){
      if(!HAS_UID || !ADMIN_PWD){
        document.getElementById('root').innerHTML = '<div class="card"><h2>等待登录</h2><div class="small">请输入管理员账号与密码，点击“设置 userId”后再点“刷新”</div></div>';
        document.getElementById('feedbackSection').style.display='none';
        document.getElementById('usersSection').style.display='none';
        document.getElementById('aiGenSection').style.display='none';
        return;
      }
      const root = document.getElementById('root'); root.innerHTML='加载中…';
      try{
        const d = await fetchJSON('/admin/summary');
        const db = d.db||{}; const ds = d.datasets||{}; const cfg = d.config||{};
        const warn = db.sizeBytes > (db.budgetBytes*0.9);
        const dbCard = el('div', { class:'card' }, [
          el('h2',{},'存储与配额'),
          kvRow('DB 大小', fmtBytes(db.sizeBytes||0)),
          kvRow('预算', fmtBytes(db.budgetBytes||0)),
          kvRow('阈值比例', db.margin),
          el('div', { class:'small ' + (warn?'warn':'ok') }, warn? '注意：接近配额，请关注逐出频率' : '配额健康'),
        ]);
        const mkCountCard = (title, obj) => el('div', { class:'card' }, [ el('h2',{},title), ...Object.entries(obj||{}).map(([k,v])=>kvRow(k,v)) ]);
        // Traffic card (last 7 days PV/UV and top paths)
        const tr = ds.traffic || {};
        const trDays = Array.isArray(tr.days) ? tr.days : [];
        const trTop = Array.isArray(tr.topPaths) ? tr.topPaths : [];
        const trafficCard = el('div', { class:'card' }, [
          el('h2',{},'流量（近7天）'),
          ...(trDays.length ? trDays.map(d => kvRow(d.day, (d.pv||0) + ' / ' + (d.uv||0))) : [ el('div', { class:'small'}, '暂无数据') ]),
          el('div', { class:'small'}, 'Top Paths'),
          ...trTop.slice(0,10).map(x => kvRow(x.path, x.count))
        ]);
        // Third-party metrics
        const tp = ds.thirdparty || {};
        const tpDays = Array.isArray(tp.days) ? tp.days : [];
        const tpTop = Array.isArray(tp.topEndpoints) ? tp.topEndpoints : [];
        const thirdpartyCard = el('div', { class:'card' }, [
          el('h2',{},'第三方接口（近7天）'),
          ...(tpDays.length ? tpDays.map(d => {
            const total = (d.providers||[]).reduce((acc, p)=>acc + (p.total||0), 0);
            return kvRow(d.day, total);
          }) : [ el('div', { class:'small'}, '暂无数据') ]),
          el('div', { class:'small'}, 'Top Endpoints'),
          ...tpTop.slice(0,10).map(x => kvRow(`${x.provider} ${x.endpoint}`, x.total))
        ]);
        const biz = el('div', { class:'grid' }, [
          mkCountCard('行程', ds.itineraries||{}),
          mkCountCard('私聊', ds.chats||{}),
          mkCountCard('群聊', ds.groupChats||{}),
          mkCountCard('远端行程快照', ds.chatRemoteItineraries||{}),
          mkCountCard('同行人/群组/用户', { 同行人: (ds.companions||{}).count||0, 群组: (ds.groups||{}).count||0, 用户: (ds.users||{}).count||0 }),
          mkCountCard('反馈', ds.feedbacks||{}),
          mkCountCard('AI 限流统计(窗口内)', ds.aiRate||{}),
          trafficCard,
          thirdpartyCard,
        ]);
        const cfgCard = el('div', { class:'card' }, [
          el('h2',{},'后端配置'),
          kvRow('每线程消息上限', cfg.perThreadLimit),
          kvRow('远端快照上限', cfg.remoteItinsLimit),
          kvRow('AI 限流/小时', cfg.aiRateMax),
          kvRow('AI 窗口秒数', cfg.aiRateWindowSecs),
        ]);
        root.innerHTML='';
        root.appendChild(dbCard);
        root.appendChild(biz);
        root.appendChild(cfgCard);
        document.getElementById('status').textContent = '已加载（管理员：' + (d.admin||'') + ')';
        // After main summary, load feedback list
        loadFeedback();
        loadUsers();
        loadAIGenLogs();
      }catch(e){
  root.innerHTML='';
  root.appendChild(el('div', { class:'card' }, [ el('h2',{},'无法加载'), el('div', { class:'small'}, '权限不足或网络异常') ]));
  document.getElementById('status').textContent = '未授权';
  document.getElementById('feedbackSection').style.display='none';
  document.getElementById('usersSection').style.display='none';
  document.getElementById('aiGenSection').style.display='none';
      }
    }
  async function setUserId(){
      const uid = (document.getElementById('uidInput').value||'').trim();
      ADMIN_PWD = (document.getElementById('pwdInput').value||'').trim();
      if(!uid){ alert('请输入 userId'); return; }
      try{
        const r = await fetch(API_BASE + '/auth/session', { method:'PUT', credentials:'include', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ userId: uid }) });
        if(!r.ok) throw new Error(await r.text());
    HAS_UID = true;
    document.getElementById('status').textContent = '已更新账户，请点击“刷新”加载数据';
      }catch(e){ alert('设置失败：' + e); }
    }

    async function loadFeedback(){
      const sec = document.getElementById('feedbackSection');
      if(!HAS_UID || !ADMIN_PWD){ sec.style.display='none'; return; }
      try{
        const list = await fetchJSON('/feedback');
        if(!Array.isArray(list)){ sec.style.display='none'; return; }
        sec.style.display='block';
        const wrap = document.getElementById('feedbackWrap');
        const latest = list.slice(-200).reverse(); // newest first
        document.getElementById('feedbackCount').textContent = `(${latest.length})`;
        if(!latest.length){ wrap.innerHTML = '<div class="small">暂无反馈</div>'; return; }
        const rows = latest.map(item => {
          const meta = item.meta && typeof item.meta==='object' ? JSON.stringify(item.meta).slice(0,200) : '';
          const ua = (item.ua||'').slice(0,120);
          const txt = String(item.text||'').replace(/</g,'&lt;').replace(/>/g,'&gt;');
          return `<tr><td>${fmtTime(item.ts)}</td><td class="monospace">${item.userId||''}</td><td class="monospace">${(item.sid||'').slice(0,10)}</td><td class="truncate" title="${ua}">${ua}</td><td>${txt}</td><td class="truncate" title="${meta}">${meta}</td></tr>`;
        }).join('');
        wrap.innerHTML = `<table class="feedback"><thead><tr><th>时间</th><th>用户</th><th>SID</th><th>UA</th><th>文本</th><th>Meta</th></tr></thead><tbody>${rows}</tbody></table>`;
      }catch(e){
        document.getElementById('feedbackWrap').innerHTML = '<div class="small warn">加载失败</div>';
      }
    }

    async function loadUsers(){
      const sec = document.getElementById('usersSection');
      if(!HAS_UID || !ADMIN_PWD){ sec.style.display='none'; return; }
      try{
        const list = await fetchJSON('/users');
        if(!Array.isArray(list)){ sec.style.display='none'; return; }
        sec.style.display='block';
        document.getElementById('usersCount').textContent = `(${list.length})`;
        const latest = list.slice(-100).reverse();
        const wrap = document.getElementById('usersWrap');
        if(!latest.length){ wrap.innerHTML = '暂无用户'; return; }
        wrap.innerHTML = latest.map(u => {
          const id = u && (u.id||u.userId||'');
          const phone = u && u.phone ? String(u.phone) : '';
          const nick = u && u.nickname ? String(u.nickname) : '';
          return `<div style="padding:2px 0;border-bottom:1px dashed #eee"><span class=monospace>${id}</span> ${nick ? '['+nick+']' : ''} ${phone}</div>`;
        }).join('');
      }catch(e){
        document.getElementById('usersWrap').innerHTML = '<div class="small warn">加载失败</div>';
      }
    }

    async function loadAIGenLogs(){
      const sec = document.getElementById('aiGenSection');
      if(!HAS_UID || !ADMIN_PWD){ sec.style.display='none'; return; }
      try{
        const limit = parseInt(document.getElementById('aiGenLimit').value||'100',10);
        const list = await fetchJSON(`/admin/aigen/logs?limit=${limit}`);
        if(!Array.isArray(list)){ sec.style.display='none'; return; }
        sec.style.display='block';
        document.getElementById('aiGenCount').textContent = `(${list.length})`;
        if(!list.length){ document.getElementById('aiGenWrap').innerHTML = '暂无生成记录'; return; }
        const html = list.map(item => {
          const ts = item.ts ? new Date(item.ts*1000).toISOString().replace('T',' ').slice(0,19) : '';
          const q = (item.query||'').replace(/</g,'&lt;');
          const preview = JSON.stringify(item.result||{}).slice(0,240).replace(/</g,'&lt;');
          return `<div style="padding:4px 0;border-bottom:1px dashed #e5e7eb"><div><strong>${ts}</strong> user=${item.userId||''} days=${item.days||''} keys=${(item.prefKeys||[]).join(',')}</div><div>Q: ${q}</div><div>R: ${preview}</div></div>`;
        }).join('');
        document.getElementById('aiGenWrap').innerHTML = html;
      }catch(e){
        document.getElementById('aiGenWrap').innerHTML = '<span class="warn">加载失败</span>';
      }
    }

    document.getElementById('btnRefresh').addEventListener('click', render);
    document.getElementById('btnSetUid').addEventListener('click', setUserId);
    document.getElementById('btnRefreshFeedback').addEventListener('click', loadFeedback);
    document.getElementById('btnRefreshAIGen').addEventListener('click', loadAIGenLogs);
    document.getElementById('aiGenLimit').addEventListener('change', loadAIGenLogs);
    // Auto refresh feedback every 30s when visible & logged in
    setInterval(()=>{ if(HAS_UID && ADMIN_PWD && document.visibilityState==='visible'){ loadFeedback(); loadUsers(); loadAIGenLogs(); } }, 30000);
  // 初始提示
  document.getElementById('root').innerHTML = '<div class="card"><h2>管理员登录</h2><div class="small">请输入账号与密码，点击“设置 userId”后再点“刷新”以加载监控数据。</div></div>';
  </script>
</body>
</html>
